﻿using Microsoft.Extensions.Logging;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Macs;
using Org.BouncyCastle.Security;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using Umi.Crypto.Abstraction.HMac;
using Umi.Crypto.Abstraction.Keys;
using Umi.Web.Metadatas.Attributes;
using Umi.Web.Utils.Utils;

namespace Umi.Web.Crypto.HMacs
{
    [Service("PBKDF2Password", Scoped = ServiceScoped.Single)]
    public class PBKDF2PasswordService : IPasswordService
    {
        private readonly ILogger logger;

        public PBKDF2PasswordService(ILogger<PBKDF2PasswordService> logger)
        {
            this.logger = logger;
        }

        public string Name => "PBKDF2";

        public byte[] GenAuthCode(byte[] passwrod, KeyParameter key, string hash)
        {
            if (!(key is PBKDF2KeyParameter))
                throw new NotSupportedException("Can not parse the parameter");
            PBKDF2KeyParameter parameter = key as PBKDF2KeyParameter;
            IDigest digest = DigestUtilities.GetDigest(hash);
            IMac mac = new HMac(digest);
            logger.LogInformation("使用 HMAC-{} 作为hash函数", digest.AlgorithmName);
            byte[] output = new byte[parameter.DkLen];
            int hLen = mac.GetMacSize();
            decimal dkLen = parameter.DkLen;
            decimal blocks = Math.Ceiling(dkLen / hLen);
            int filled = 0;
            // PBKDF2变种实现
            for (int i = 0; i < blocks; i++)
            {
                byte[] tk = BlockFunction(mac, passwrod, parameter.Key, parameter.Salt, parameter.Count, i);
                Array.Copy(tk, 0, output, filled, tk.Length > (output.Length - filled) ? (output.Length - filled) : tk.Length);
                filled += tk.Length > (output.Length - filled) ? (output.Length - filled) : tk.Length;
            }
            return output;
        }

        private byte[] BlockFunction(IMac mac, byte[] password, byte[] key, byte[] salt, int count, decimal blocks)
        {
            byte[] blockBig = BitConverter.GetBytes(Convert.ToInt32(blocks)).Reverse().ToArray();
            byte[] ckey = new byte[key.Length + salt.Length + blockBig.Length];
            Array.Copy(key, 0, ckey, 0, key.Length);
            Array.Copy(salt, 0, ckey, key.Length, salt.Length);
            Array.Copy(blockBig, 0, ckey, key.Length + salt.Length, blockBig.Length);
            mac.Init(new Org.BouncyCastle.Crypto.Parameters.KeyParameter(ckey));
            byte[] last = null;
            for (int i = 0; i < count; i++)
            {
                byte[] output = new byte[mac.GetMacSize()];
                mac.BlockUpdate(password, 0, password.Length);
                mac.DoFinal(output, 0);
                if (last != null)
                    last = ByteArrayUtils.ArrayXor(output, last);
                else
                    last = output;
                mac.Reset();
                mac.Init(new Org.BouncyCastle.Crypto.Parameters.KeyParameter(last));
            }
            return last;
        }
    }
}
